package lru

import (
	"container/list"
)

type Cache struct {
	maxBytes  int64
	udBytes   int64
	lis       *list.List
	cache     map[string]*list.Element
	OnEvicted func(key string, value Value)
}

type entry struct {
	key   string
	value Value
}

type Value interface {
	Len() int
}

func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
	return &Cache{
		maxBytes:  maxBytes,
		udBytes:   0,
		lis:       list.New(),
		cache:     make(map[string]*list.Element),
		OnEvicted: onEvicted,
	}
}

func (c *Cache) Find(key string) (value Value, ok bool) {
	if val, ok := c.cache[key]; ok {
		c.lis.MoveToBack(val)
		kv := val.Value.(*entry)
		// fmt.Printf("the key: %v,  and the value: %v", kv.key, kv.value)
		return kv.value, true
	}
	return nil, false
}

func (c *Cache) Delete() {
	val := c.lis.Front()
	if val != nil {
		c.lis.Remove(val)
		kv := val.Value.(*entry)
		delete(c.cache, kv.key)
		c.udBytes -= int64(len(kv.key)) + int64(kv.value.Len())
		if c.OnEvicted != nil {
			c.OnEvicted(kv.key, kv.value)
		}
	}
}

func (c *Cache) Add(key string, value Value) {
	if val, ok := c.cache[key]; ok {
		c.lis.MoveToBack(val)
		kv := val.Value.(*entry)
		c.udBytes += int64(value.Len()) - int64(kv.value.Len())
		kv.value = value
	} else {
		val := c.lis.PushBack(&entry{key, value})
		c.cache[key] = val
		c.udBytes += int64(len(key)) + int64(value.Len())
	}
	for c.maxBytes != 0 && c.maxBytes < c.udBytes {
		c.Delete()
	}
}

func (c *Cache) Len() int {
	return c.lis.Len()
}

